use super::{
    cur_time, err, err_value, json, ok, throw_err, try_rm_task, Base, CBase, Client, ECode, Lock,
    MapSv, RNull, Resp, TBase, VBase, VMap, Value, VecV,
};
use std::collections::{HashMap, HashSet};
use tracing::{instrument, warn};

type HSV = HashMap<String, Vec<Value>>;

pub struct Goods;

impl Base for Goods {}
impl TBase for Goods {}

impl Goods {
    pub async fn do_cmd(&self, para: &Value, cmd: &str, client: &Client, lock: Lock) -> Resp {
        let data = para.ckobj()?;
        let line = data.ck_str("line")?;

        match cmd {
            "addbox" => self.do_addbox(line, data, client, lock).await,
            "addpsn" => self.do_addpsn(line, data, client, lock).await,
            "fin" => self.do_fin(line, data, client, lock).await,
            "free" => self.do_free(line, data, client, lock).await,
            "mark" => self.do_mark(line, data, client).await,
            "list" => self.do_list(line, client, lock).await,
            "listpsn" => self.do_listpsn(line, data, client, lock).await,
            "loadpsn" => self.do_loadpsn(line, data, client).await,
            "order" => self.do_order(line, data, client).await,
            "psn" => self.do_psn(data),
            "qbc" | "db" | "dt" => self.calc_data(cmd, line, data, client, lock).await,
            "task" => self.do_task(line, data, client, lock).await,
            "intrace" => self.do_intrace(line, data, client).await,
            _ => throw_err!(ECode::NoServe, "no goods serve"),
        }
    }

    #[instrument(skip_all, name = "trace_tab")]
    async fn trace_tab(&self, ret: &mut HSV, data: &MapSv, client: &Client) -> RNull {
        let s = data.ck_str("s")?;
        let e = data.ck_str("e")?;
        let opt = data.ck_val("opt")?;

        let body = client.timedb(opt, "get").await?;
        let body = body.ck_obj("data")?;

        for (_, it) in body.iter() {
            if let Some(data) = it.k_array("data") {
                for d in data.iter() {
                    let d = d.ckobj()?;

                    let intime = match d.k_str("intime") {
                        Some(v) => v,
                        None => {
                            continue;
                        }
                    };

                    if intime < s || intime > e {
                        continue;
                    }

                    let psn = d.ck_str("psn").unwrap_or("");
                    let tsn = d.ck_str("tsn").unwrap_or("");
                    let org = d.ck_str("org").unwrap_or("");

                    if psn.is_empty() || tsn.is_empty() {
                        continue;
                    }

                    ret.entry(tsn.to_string())
                        .or_insert(Vec::new())
                        .push(json!({
                            "sn": psn,
                            "org": org,
                        }));
                }
            }
        }

        Ok(())
    }

    #[instrument(skip_all, name = "trace_goods")]
    async fn trace_goods(&self, line: &str, ret: &mut HSV, data: &MapSv, client: &Client) -> RNull {
        let s = data.ck_str("s")?;
        let e = data.ck_str("e")?;

        let cmd = json!({ "line": line });
        let body = client.cfgdb(&cmd, "goods/list").await?;
        let body = body.ck_array("data")?;

        for d in body.iter() {
            let sn = d.ck_str("sn")?;

            let cmd = json!({ "line": line, "k": sn });

            let body_psn = match client.cfgdb(&cmd, "goods/listpsn").await {
                Ok(v) => v,
                Err(_) => {
                    continue;
                }
            };

            let body_psn = body_psn.ck_array("data")?;

            for d in body_psn.iter() {
                let intime = match d.k_str("intime") {
                    Some(v) => v,
                    None => {
                        continue;
                    }
                };

                if intime < s || intime > e {
                    continue;
                }

                let psn = d.ck_str("sn").unwrap_or("");
                let tsn = d.ck_str("tech").unwrap_or("");
                let org = d.ck_str("org").unwrap_or("");

                if psn.is_empty() || tsn.is_empty() {
                    continue;
                }

                ret.entry(tsn.to_string())
                    .or_insert(Vec::new())
                    .push(json!({
                        "sn": psn,
                        "org": org,
                    }));
            }
        }

        Ok(())
    }

    #[instrument(skip_all, name = "do_intrace")]
    async fn do_intrace(&self, line: &str, data: &MapSv, client: &Client) -> Resp {
        let mut val = HashMap::new();

        let _ = self.trace_tab(&mut val, data, client).await;

        let _ = self.trace_goods(line, &mut val, data, client).await;

        let mut ret = Vec::new();

        for (k, val) in val.iter() {
            let val = match val.array() {
                Some(v) => v,
                None => {
                    continue;
                }
            };

            let mut n = 0;
            let mut away = 0;
            let mut set = HashSet::new();

            for d in val.iter() {
                let d = d.ckobj()?;

                let sn = d.ck_str("sn")?;
                let org = d.ck_str("org")?;

                if set.contains(sn) {
                    continue;
                }

                n += 1;

                if org == "away" {
                    away += 1;
                }

                set.insert(sn.to_string());
            }

            ret.push(json!({ "sn": k, "num": n, "away": away }));
        }

        if ret.is_empty() {
            throw_err!(ECode::EmptyData, "no data");
        }

        ok!(ret)
    }

    fn ck_tag_sort(&self, tech_sn: &str, data: &Vec<&str>) -> Option<Value> {
        if data.len() < 10 {
            return None;
        }

        let group_sn = data[3];
        let sn = data[4];
        let outdia = data[5];
        let insn = data[6];
        let india = data[7];
        let play = data[8];
        let angle = data[9];

        Some(json!({
            "sn": sn,
            "insn": insn,
            "tech": tech_sn,
            "outdia": outdia,
            "india": india,
            "groupsn": group_sn,
            "play": play,
            "angle": angle,
        }))
    }

    fn ck_tag_pair(&self, tech_sn: &str, data: &Vec<&str>) -> Option<Value> {
        if data.len() < 8 {
            return None;
        }

        let group_sn = data[3];
        let sn_a = data[4];

        let val_a: Vec<&str> = data[5].split("*").collect();
        if val_a.len() < 3 {
            return None;
        }
        let outdia_a = val_a[0];
        let india_a = val_a[1];
        let outwidth_a = val_a[2];

        let sn_b = data[6];

        let val_b: Vec<&str> = data[7].split("*").collect();
        if val_b.len() < 7 {
            return None;
        }
        let outdia_b = val_b[0];
        let india_b = val_b[1];
        let outwidth_b = val_b[2];

        let standout_a = val_b[3];
        let dent_a = val_b[4];
        let standout_b = val_b[5];
        let dent_b = val_b[6];

        Some(json!({
            "dt": true,
            "a": {
                "sn": sn_a,
                "tech": tech_sn,
                "dent": dent_a,
                "standout": standout_a,
                "outdia": outdia_a,
                "india": india_a,
                "width": outwidth_a,
                "groupsn": group_sn,
                "gtp": "A",
                "group": "pair",
                "other": sn_b,
            },
            "b": {
                "sn": sn_b,
                "tech": tech_sn,
                "dent": dent_b,
                "standout": standout_b,
                "outdia": outdia_b,
                "india": india_b,
                "width": outwidth_b,
                "groupsn": group_sn,
                "gtp": "B",
                "group": "pair",
                "other": sn_a,
            },
        }))
    }

    fn ck_tag(&self, data: &str) -> Option<Value> {
        warn!("qr: {data}");

        let qr: Vec<&str> = data.split("#").collect();
        if qr.len() < 5 {
            return None;
        }

        let tech_sn = qr[1];
        let group = qr[2];

        match group {
            "N" | "NN" => {
                return self.ck_tag_sort(tech_sn, &qr);
            }
            _ => {
                if !group.is_empty() {
                    return self.ck_tag_pair(tech_sn, &qr);
                }
            }
        }

        let sn = qr[3];

        let val: Vec<&str> = qr[4].split("*").collect();
        if val.len() < 5 {
            return None;
        }

        let outdia = val[0];
        let india = val[1];
        let outwidth = val[2];
        let standout = val[3];
        let dent = val[4];

        Some(json!({
            "sn": sn,
            "tech": tech_sn,
            "standout": standout,
            "dent": dent,
            "outdia": outdia,
            "india": india,
            "width": outwidth,
        }))
    }

    #[instrument(skip_all, name = "do_mark")]
    async fn do_mark(&self, line: &str, data: &MapSv, client: &Client) -> Resp {
        let sn = data.ck_str("sn")?;
        let lub = data.k_str("lub");
        let seal = data.k_str("seal");

        let cmd = json!({ "line": line, "k": sn });

        let body = client.cfgdb(&cmd, "psn/get").await?;
        let body = body.ck_obj("data")?;

        let tech_sn = body.ck_str("tech")?;

        let cmd = json!({ "line": line, "k": tech_sn });

        let tech_body = client.cfgdb(&cmd, "tech/get").await?;
        let tech_body = tech_body.ck_obj("data")?;

        let tp = match tech_body.k_str_empty_trim("tp") {
            Some(v) => v,
            None => "angle",
        };

        let ret = match tp {
            "angle" => {
                let standout = body.ck_f64("standout")?.round() as i64;
                let dent = body.ck_f64("dent")?.round() as i64;
                let angle = body.ck_f64("angle")?.round() as i64;
                let outdia = body.ck_f64("outdia")?.trunc() as i64;
                let outwidth = body.ck_f64("outwidth")?.round() as i64;
                let india = body.ck_f64("india")?.trunc() as i64;
                let group = "";

                let qr = format!(
                    "#{}#{}#{}#{}*{}*{}*{}*{}#",
                    tech_sn, group, sn, outdia, india, outwidth, standout, dent
                );

                json!({
                    "tech": tech_body.ck_str("sn")?,
                    "tag": tech_body.ck_str("tag")?,
                    "standout": standout,
                    "dent": dent,
                    "angle": angle,
                    "outdia": outdia,
                    "outwidth": outwidth,
                    "india": india,
                    "sn": sn,
                    "qr": qr,
                    "tmpl": "angle",
                })
            }
            "sort" => {
                let outdia = body.ck_f64("outdia")?.trunc() as i64;
                let india = body.ck_f64("india")?.trunc() as i64;
                let play = body.ck_f64("play")?.trunc() as i64;
                let angle = body.ck_f64("angle")?.trunc() as i64;
                let group_sn = body.ck_str("group")?;
                let insn = body.ck_str("insn")?;
                let tech = body.ck_str("tech")?;

                let tech: Vec<&str> = tech.split("-").collect();
                if tech.len() < 2 {
                    throw_err!(ECode::Para, "tech format error");
                }
                let tech_sn = tech[0];

                let group = match tech[1].find("NN") {
                    Some(_) => "NN",
                    None => "N",
                };

                let qr = format!(
                    "#{}#{}#{}#{}#{}#{}#{}#{}#{}#",
                    tech_sn, group, group_sn, sn, outdia, insn, india, play, angle
                );

                json!({
                    "tech": format!("{} {}", group, tech_sn),
                    "tag": tech_body.ck_str("tag")?,
                    "play": play,
                    "angle": angle,
                    "outdia": outdia,
                    "india": india,
                    "groupsn": group_sn,
                    "sn": sn,
                    "insn": insn,
                    "qr": qr,
                    "tmpl": "sort",
                })
            }
            _ => throw_err!(ECode::Para, "tech type error"),
        };

        let mut val = HashMap::new();

        if let Some(v) = lub {
            val.insert("lub", v);
        }

        if let Some(v) = seal {
            val.insert("seal", v);
        }

        if !val.is_empty() {
            let cmd = json!({
                "line": line,
                "k": sn,
                "v": val,
            });
            let _ = client.cfgdb(&cmd, "psn/md").await;
        }

        ok!(ret)
    }

    #[instrument(skip_all, name = "calc_data")]
    async fn calc_data(
        &self,
        tp: &str,
        line: &str,
        data: &MapSv,
        client: &Client,
        lock: Lock,
    ) -> Resp {
        let body = self.do_listpsn(line, data, client, lock).await?;
        let body = body.ck_array("data")?;

        let base = CBase;

        match tp {
            "qbc" => base.qbc(data, &body).await,
            "dt" => base.dt(data, &body).await,
            "db" => base.db(data, &body).await,
            _ => throw_err!(ECode::Para, "tp error"),
        }
    }

    #[instrument(skip_all, name = "do_listpsn")]
    async fn do_listpsn(&self, line: &str, data: &MapSv, client: &Client, lock: Lock) -> Resp {
        let _ = lock.lock().await;

        let sn = data.ck_str("k")?;

        let cmd = json!({
            "line": line,
            "k": sn,
        });

        let body = client.cfgdb(&cmd, "goods/listpsn").await?;
        let body = body.ck_array("data")?;

        let set = match self.order_set(line, client).await {
            Ok(v) => v,
            Err(_) => HashSet::new(),
        };

        let mut ret = Vec::new();

        for d in body.iter() {
            let psn = d.ck_str("sn")?;
            if set.contains(psn) {
                continue;
            }

            ret.push(d);
        }

        if ret.is_empty() {
            throw_err!(ECode::EmptyData, "no data");
        }

        ok!(ret)
    }

    #[instrument(skip_all, name = "do_free")]
    async fn do_free(&self, line: &str, data: &MapSv, client: &Client, lock: Lock) -> Resp {
        let _ = lock.lock().await;

        let box_sn = data.ck_str("k")?;

        let cmd = json!({ "line": line });

        client
            .cfgdb(&cmd, &format!("file/rmfile/gbox_{box_sn}"))
            .await?;

        ok!()
    }

    #[instrument(skip_all, name = "do_fin")]
    async fn do_fin(&self, line: &str, data: &MapSv, client: &Client, lock: Lock) -> Resp {
        let _ = lock.lock().await;

        let order_sn = data.ck_str("k")?;

        let body = match self.loadpsn(line, order_sn, client).await {
            Ok(v) => v,
            Err(_) => {
                warn!("fin: no psn");

                let cmd = json!({
                    "line": line,
                    "sn": order_sn,
                });

                let _ = client.cfgdb(&cmd, &format!("file/rm/gorder")).await?;
                return ok!();
            }
        };

        let mut ret = Vec::new();
        let mut psn_vec = Vec::new();

        warn!("fin: get psn");

        for d in body.iter() {
            let d = d.ckobj()?;
            let psn = d.ck_str("sn")?;

            let st = match d.k_str("st") {
                Some(v) => v,
                None => {
                    continue;
                }
            };

            if st != "out" {
                continue;
            }

            let mut val = HashMap::new();

            for (k, v) in d.iter() {
                if k == "tsn" || k == "psn" || k == "wsn" || k == "line" || k == "usn" {
                    continue;
                }

                let t = match v.kstr() {
                    Some(tv) => tv.to_string(),
                    None => v.to_string(),
                };

                val.insert(k, t);
            }

            let tmp = json!({
                "psn": psn,
                "wsn": order_sn,
                "k":  {
                    "tsn": d.ck_str("tech")?,
                },
                "v": val,
            });

            ret.push(tmp);
            psn_vec.push(psn);
        }

        warn!("fin: chunk data");

        let tab = cur_time("%Y");

        for d in ret.chunks(300) {
            let cmd = json!({
                "line": line,
                "tab": tab,
                "data": d,
            });
            let _ = client.timedb(&cmd, "chunk").await?;
        }

        warn!("fin: rm psn");

        for d in psn_vec.iter() {
            let cmd = json!({
                "line": line,
                "k": d,
            });

            let _ = client.cfgdb(&cmd, "psn/rm").await?;
        }

        warn!("fin: rm order");

        let cmd = json!({
            "line": line,
            "sn": order_sn,
        });

        client.cfgdb(&cmd, &format!("file/rm/gorder")).await
    }

    async fn order_set(&self, line: &str, client: &Client) -> Result<HashSet<String>, Value> {
        let cmd = json!({ "line": line });

        let mut set = HashSet::new();

        if let Ok(body) = client.cfgdb(&cmd, &format!("file/list/gorder")).await {
            let body = body.ck_array("data")?;

            for d in body.iter() {
                if let Ok(val) = d.ck_array("v") {
                    for d in val.iter() {
                        let d = d.ckstr()?;
                        set.insert(d.to_string());
                    }
                }
            }
        }

        Ok(set)
    }

    #[instrument(skip_all, name = "do_list")]
    async fn do_list(&self, line: &str, client: &Client, lock: Lock) -> Resp {
        let _ = lock.lock().await;

        let cmd = json!({ "line": line });

        let body = client.cfgdb(&cmd, "goods/list").await?;
        let body = body.ck_array("data")?;

        let set = match self.order_set(line, client).await {
            Ok(v) => v,
            Err(_) => HashSet::new(),
        };

        let mut ret = Vec::new();

        for d in body.iter() {
            let d = d.ckobj()?;

            let sn = d.ck_str("sn")?;
            let mut num = 0;

            let cmd = json!({
                "line": line,
                "k": sn,
            });

            if let Ok(body_psn) = client.cfgdb(&cmd, "goods/listpsn").await {
                let body_psn = body_psn.ck_array("data")?;

                for it in body_psn.iter() {
                    let psn = it.ck_str("sn")?;
                    if set.contains(psn) {
                        continue;
                    }

                    num += 1;
                }
            }

            let mut tmp = d.clone();
            tmp.insert("num".to_string(), json!(num));

            ret.push(tmp);
        } // for

        ok!(ret)
    }

    #[instrument(skip_all, name = "do_order")]
    async fn do_order(&self, line: &str, data: &MapSv, client: &Client) -> Resp {
        let count = match data.k_bool("count") {
            Some(v) => v,
            None => false,
        };

        let cmd = json!({ "line": line });
        let body = client.cfgdb(&cmd, &format!("file/list/gorder")).await?;
        let body = body.ck_array("data")?;

        if !count {
            return ok!(body);
        }

        let mut ret = Vec::new();

        for d in body.iter() {
            let d = d.ckobj()?;

            let mut num = 0;

            if let Ok(val) = d.ck_array("v") {
                for d in val.iter() {
                    let cmd = json!({
                        "line": line,
                        "k": d,
                    });

                    if let Ok(psn_body) = client.cfgdb(&cmd, "psn/get").await {
                        let psn_body = psn_body.ck_obj("data")?;

                        if let Some(st) = psn_body.k_str("st") {
                            if st == "out" {
                                num += 1
                            }
                        }
                    }
                }
            }

            let mut tmp = d.clone();
            tmp.insert("dnum".to_string(), json!(num));

            ret.push(tmp);
        }

        ok!(ret)
    }

    #[instrument(skip_all, name = "do_loadpsn")]
    async fn do_loadpsn(&self, line: &str, data: &MapSv, client: &Client) -> Resp {
        let sn = data.ck_str("k")?;

        let ret = self.loadpsn(line, sn, client).await?;
        ok!(ret)
    }

    #[instrument(skip_all, name = "loadpsn")]
    async fn loadpsn(&self, line: &str, sn: &str, client: &Client) -> Result<VecV, Value> {
        let cmd = json!({
            "line": line,
            "sn": sn,
        });

        let body = client.cfgdb(&cmd, &format!("file/get/gorder")).await?;
        let val = body.ck_obj("data")?.ck_array("v")?;

        let mut ret = Vec::new();

        for d in val.iter() {
            let cmd = json!({
                "line": line,
                "k": d,
            });

            let psn_body = client.cfgdb(&cmd, "psn/get").await?;
            let psn_body = psn_body.ck_obj("data")?;

            ret.push(json!(psn_body));
        }

        Ok(ret)
    }

    #[instrument(skip_all, name = "do_task")]
    async fn do_task(&self, line: &str, data: &MapSv, client: &Client, lock: Lock) -> Resp {
        let _ = lock.lock().await;

        let tech = data.ck_str("k")?;
        let val = data.ck_array("v")?;

        warn!("task step: gen task sn");

        let cmd = json!({
            "line": line,
            "rule": "task",
        });

        let body = client.cfgdb(&cmd, "code/gen").await?;

        let task_sn = body.ck_obj("data")?.ck_str("psn")?;
        let task_sn = format!("D{task_sn}");

        let cmd = json!({
            "line": line,
            "sn": task_sn,
            "v": {
                "sn": task_sn,
                "tech": tech,
                "v": val,
            }
        });

        client.cfgdb(&cmd, &format!("file/md/gorder")).await
    }

    fn ck_qr_pair(&self, data: &str) -> Option<Value> {
        let qr = data.to_string();

        if qr.len() < 45 {
            return Some(json!({ "dt": false, }));
        }

        let pos = qr.find('-')?;
        let (tech, qr) = qr.split_at_checked(pos + 8)?;

        if !tech.ends_with("DT") {
            return Some(json!({ "dt": false, }));
        }

        let tech = tech.strip_suffix("DT")?;
        let tech = tech.trim();
        let tech = tech.trim_matches('_');

        let (groupsn, qr) = qr.split_at_checked(6)?;

        let pos = qr.find('-')?;
        let (sn, code) = qr.split_at_checked(pos)?;
        let sn = sn.trim_matches('_');

        if !code.starts_with("-") {
            return None;
        }

        let (pre, code) = code.split_at_checked(1)?;
        let pos = code.find('-')?;
        let (tmp, code) = code.split_at_checked(pos)?;
        let outdia = format!("{pre}{tmp}");

        let (pre, code) = code.split_at_checked(1)?;
        let pos = code.find('-')?;
        let (tmp, code) = code.split_at_checked(pos)?;
        let india = format!("{pre}{tmp}");

        let (width, code) = code.split_at_checked(3)?;
        let (angle, code) = code.split_at_checked(5)?;

        let (sea, code) = code.split_at_checked(1)?;
        let (kea, code) = code.split_at_checked(1)?;
        let (sia, code) = code.split_at_checked(1)?;
        let (kia, code) = code.split_at_checked(1)?;

        let pos = code.find('-')?;
        let (b_sn, code) = code.split_at_checked(pos)?;
        let b_sn = b_sn.trim_matches('_');

        if !code.starts_with("-") {
            return None;
        }

        let (pre, code) = code.split_at_checked(1)?;
        let pos = code.find('-')?;
        let (tmp, code) = code.split_at_checked(pos)?;
        let b_outdia = format!("{pre}{tmp}");

        let (pre, code) = code.split_at_checked(1)?;
        let pos = code.find('-')?;
        let (tmp, code) = code.split_at_checked(pos)?;
        let b_india = format!("{pre}{tmp}");

        let (b_width, code) = code.split_at_checked(3)?;
        let (b_angle, code) = code.split_at_checked(5)?;

        let (b_sea, code) = code.split_at_checked(1)?;
        let (b_kea, code) = code.split_at_checked(1)?;
        let (b_sia, code) = code.split_at_checked(1)?;
        let (b_kia, code) = code.split_at_checked(1)?;

        let (pre, code) = code.split_at_checked(1)?;
        let pos = match code.find('-') {
            Some(v) => v,
            None => code.find('+')?,
        };
        let (tmp, code) = code.split_at_checked(pos)?;
        let b_standout = format!("{pre}{tmp}");

        let (pre, code) = code.split_at_checked(1)?;
        let pos = match code.find('-') {
            Some(v) => v,
            None => code.find('+')?,
        };
        let (tmp, standout) = code.split_at_checked(pos)?;
        let b_dent = format!("{pre}{tmp}");

        Some(json!({
            "dt": true,
            "a": {
                "sn": sn,
                "tech": tech,
                "sea": sea,
                "kea": kea,
                "sia": sia,
                "kia": kia,
                "angle": angle,
                "standout": standout,
                "outdia": outdia,
                "india": india,
                "width": width,
                "groupsn": groupsn,
                "gtp": "A",
                "group": "pair",
                "other": b_sn,
            },
            "b": {
                "sn": b_sn,
                "tech": tech,
                "sea": b_sea,
                "kea": b_kea,
                "sia": b_sia,
                "kia": b_kia,
                "angle": b_angle,
                "standout": b_standout,
                "dent": b_dent,
                "outdia": b_outdia,
                "india": b_india,
                "width": b_width,
                "groupsn": groupsn,
                "gtp": "B",
                "group": "pair",
                "other": sn,
            },
        }))
    }

    #[instrument(skip_all, name = "ck_qr")]
    fn ck_qr(&self, data: &str) -> Option<Value> {
        if let Some(v) = self.ck_tag(data) {
            return Some(v);
        }

        let ret = self.ck_qr_pair(data)?;
        let dt = ret.k_bool("dt")?;
        if dt {
            return Some(ret);
        }

        let mut qr = data.to_string();
        warn!("qr: {qr}");

        let sea = qr.pop()?;
        let kea = qr.pop()?;
        let sia = qr.pop()?;
        let kia = qr.pop()?;
        let (qr, angle) = qr.split_at_checked(qr.len() - 5)?;
        warn!("angle: {angle}");

        let pos = qr.find('-')?;
        let (tech, qr) = qr.split_at_checked(pos + 5)?;

        let tech = tech.trim();
        let tech = tech.trim_matches('_');
        warn!("tech: {tech}");

        let pos = qr.find('-')?;
        let (sn, code) = qr.split_at_checked(pos)?;

        let sn = sn.trim();
        let sn = sn.trim_matches('_');

        warn!("sn: {sn}");
        warn!("code: {code}");

        if !code.starts_with("-") {
            return None;
        }

        let (pre, code) = code.split_at_checked(1)?;

        let mut pos = code.find('-')?;

        let code_tmp = code;
        let (tmp, _) = code_tmp.split_at_checked(pos)?;
        if let Some(_) = tmp.find('+') {
            pos = code.find('+')?;
        }

        let (tmp, code) = code.split_at_checked(pos)?;
        let outdia = format!("{pre}{tmp}");
        warn!("outdia: {outdia}");

        let (pre, code) = code.split_at_checked(1)?;
        let pos = code.find('-')?;
        let (tmp, code) = code.split_at_checked(pos)?;
        let india = format!("{pre}{tmp}");
        warn!("india: {india}");

        let (pre, code) = code.split_at_checked(1)?;
        let pos = match code.find('-') {
            Some(v) => {
                if let Some(p) = code.find('+') {
                    let ret = if p < v { p } else { v };
                    ret
                } else {
                    v
                }
            }
            None => code.find('+')?,
        };
        let (tmp, code) = code.split_at_checked(pos)?;
        let width = format!("{pre}{tmp}");
        warn!("width: {width}");

        let (pre, code) = code.split_at_checked(1)?;
        let pos = match code.find('-') {
            Some(v) => v,
            None => code.find('+')?,
        };
        let (tmp, dent) = code.split_at_checked(pos)?;
        let standout = format!("{pre}{tmp}");
        warn!("standout: {standout}");

        Some(json!({
            "sn": sn,
            "tech": tech,
            "sea": sea,
            "kea": kea,
            "sia": sia,
            "kia": kia,
            "angle": angle,
            "standout": standout,
            "dent": dent,
            "outdia": outdia,
            "india": india,
            "width": width,
        }))
    }

    #[instrument(skip_all, name = "do_psn")]
    fn do_psn(&self, data: &MapSv) -> Resp {
        let psn = data.ck_str("k")?;

        let code = self.ck_qr(psn).ok_or(err_value(ECode::Para, "qr errro"))?;

        ok!(code)
    }

    #[instrument(skip_all, name = "do_addpsn")]
    async fn do_addpsn(&self, line: &str, data: &MapSv, client: &Client, lock: Lock) -> Resp {
        let _ = lock.lock().await;

        let psn_v = data.ck_array("v")?;
        let box_sn = data.ck_str("box")?;
        let pos = data.k_str("pos").unwrap_or("");
        let seal = data.k_str_empty_trim("seal").unwrap_or("");
        let lub = data.k_str_empty_trim("lub").unwrap_or("");
        let tag = data.k_str_empty_trim("tag").unwrap_or("");
        let mfc = data.k_str_empty_trim("mfc").unwrap_or("");
        let qyd = data.k_str_empty_trim("qyd").unwrap_or("");

        let mut first = false;
        let len = psn_v.len();

        for val in psn_v.iter() {
            let val = val.ckobj()?;

            let psn = val.ck_str("sn")?;
            let tech = val.ck_str("tech")?;

            let task_sn = if len == 1 {
                tech.to_string()
            } else {
                format!("{tech}DT")
            };

            warn!("addpsn: get goods");

            if !first {
                let cmd = json!({
                    "line": line,
                    "k": task_sn,
                });

                if let Err(_) = client.cfgdb(&cmd, "goods/get").await {
                    warn!("addpsn: create new goods");

                    let cmd = json!({
                        "line": line,
                        "v": {
                            "sn": task_sn,
                        }
                    });
                    client.cfgdb(&cmd, "goods/add").await?;
                }
            }

            let cmd = json!({
                "line": line,
                "k": psn,
            });

            if let Ok(psn_body) = client.cfgdb(&cmd, "psn/get").await {
                warn!("addpsn: get psn");

                let psn_body = psn_body.ck_obj("data")?;
                let old_task = psn_body.ck_str("task")?;

                warn!("addpsn: update psn");

                let cmd = json!({
                    "line": line,
                    "k": psn,
                    "v": {
                        tag: self.cur_time("%Y-%m-%d %H:%M:%S"),
                        "boxpack": "",
                        "boxpair": "",
                        "boxgood": box_sn,
                        "task": task_sn,
                        "seal": seal,
                        "lub": lub,
                        "mfc": mfc,
                        "qyd": qyd,
                    }
                });

                let _ = client.cfgdb(&cmd, "psn/md").await?;

                if task_sn != old_task {
                    warn!("addpsn: move psn to goods");

                    let cmd = json!({
                        "line": line,
                        "k": task_sn,
                        "v": [psn]
                    });

                    let _ = client.cfgdb(&cmd, "goods/movepsn").await?;

                    warn!("addpsn: remove psn form task");

                    let cmd = json!({
                        "line": line,
                        "k": old_task,
                        "v": [psn],
                        "rmpsn": false,
                    });

                    if let Err(e) = client.cfgdb(&cmd, "task/rmpsn").await {
                        warn!("addpsn: remove psn error: {:?}", e);
                    }

                    let _ = try_rm_task(line, old_task, client).await;
                }
            } else {
                warn!("addpsn: add new psn to goods");

                let mut n_val = val.clone();

                n_val.insert("sn".to_string(), json!(psn));
                n_val.insert("task".to_string(), json!(task_sn));
                n_val.insert("boxgood".to_string(), json!(box_sn));
                n_val.insert("seal".to_string(), json!(seal));
                n_val.insert("lub".to_string(), json!(lub));
                n_val.insert("mfc".to_string(), json!(mfc));
                n_val.insert("qyd".to_string(), json!(qyd));
                n_val.insert(tag.to_string(), json!(self.cur_time("%Y-%m-%d %H:%M:%S")));

                let cmd = json!({
                    "line": line,
                    "k": task_sn,
                    "v": [n_val],
                });

                client.cfgdb(&cmd, "goods/addpsn").await?;
            }

            warn!("addpsn: put psn to goods box");

            if !first {
                let cmd = json!({
                    "line": line,
                    "v": {
                        psn: {
                            "sn": psn,
                            "pos": pos,
                        },
                        "attr": {
                            "tp": "goods",
                            "task": task_sn,
                        }
                    }
                });

                let _ = client
                    .cfgdb(&cmd, &format!("file/modify/gbox_{box_sn}"))
                    .await?;
            }

            first = true;
        } // for

        ok!()
    }

    #[instrument(skip_all, name = "do_addbox")]
    async fn do_addbox(&self, line: &str, data: &MapSv, client: &Client, lock: Lock) -> Resp {
        let _ = lock.lock().await;

        let box_sn = data.ck_str("k")?;

        let mut tmp = vec![box_sn.to_string()];
        let mut find = false;

        let cmd = json!({ "line": line });
        if let Ok(body) = client.cfgdb(&cmd, &format!("field/list/gbox")).await {
            let body = body.ck_array("data")?;

            for d in body.iter() {
                let d = d.ckstr()?;

                if d == box_sn {
                    find = true;
                }

                tmp.push(d.to_string());
            }
        }

        if !find {
            tmp.sort_unstable();
            tmp.dedup();

            let cmd = json!({
                "line": line,
                "v": tmp,
            });
            client.cfgdb(&cmd, &format!("field/md/gbox")).await?;
        }

        let cmd = json!({
            "line": line,
            "sn": "attr",
            "v": {
                "sn": "attr",
            },
        });

        client
            .cfgdb(&cmd, &format!("file/md/gbox_{box_sn}"))
            .await?;

        let cmd = json!({ "line": line });
        client
            .cfgdb(&cmd, &format!("file/list/gbox_{box_sn}"))
            .await
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn psn_test_m32() {
        let v = Goods;

        let psn = "7014-15KMKGH1H9K1-2+0-27+1+117.401111";

        let val = v.ck_qr(psn);

        assert_eq!(
            val,
            Some(json!({
                "tech": "7014-15KM",
                "sn": "KGH1H9K1",
                "outdia": "-2",
                "india": "+0",
                "width": "-27",
                "standout": "+1",
                "dent": "+1",
                "angle": "17.40",
                "kia": "1",
                "sia": "1",
                "kea": "1",
                "sea": "1",
            }))
        );

        let psn = "7014-15KMKGG2H6K4-2+0-26+0+017.731111";

        let val = v.ck_qr(psn);

        assert_eq!(
            val,
            Some(json!({
                "tech": "7014-15KM",
                "sn": "KGG2H6K4",
                "outdia": "-2",
                "india": "+0",
                "width": "-26",
                "standout": "+0",
                "dent": "+0",
                "angle": "17.73",
                "kia": "1",
                "sia": "1",
                "kea": "1",
                "sea": "1",
            }))
        );

        let psn = "7013-13KHHDT1L1K5-4-5-27+0+047.402112";

        let val = v.ck_qr(psn);

        assert_eq!(
            val,
            Some(json!({
                "tech": "7013-13KH",
                "sn": "HDT1L1K5",
                "outdia": "-4",
                "india": "-5",
                "width": "-27",
                "standout": "+0",
                "dent": "+0",
                "angle": "47.40",
                "kia": "2",
                "sia": "1",
                "kea": "1",
                "sea": "2",
            }))
        );

        let psn = "7010-18KHDD20H0L1-3-3-28-5-524.691111";

        let val = v.ck_qr(psn);

        assert_eq!(
            val,
            Some(json!({
                "tech": "7010-18KH",
                "sn": "DD20H0L1",
                "outdia": "-3",
                "india": "-3",
                "width": "-28",
                "standout": "-5",
                "dent": "-5",
                "angle": "24.69",
                "kia": "1",
                "sia": "1",
                "kea": "1",
                "sea": "1",
            }))
        );

        let psn = "7010-18KHDD20H0L1-3-3-28+5+524.691111";
        let val = v.ck_qr(psn);

        assert_eq!(
            val,
            Some(json!({
                "tech": "7010-18KH",
                "sn": "DD20H0L1",
                "outdia": "-3",
                "india": "-3",
                "width": "-28",
                "standout": "+5",
                "dent": "+5",
                "angle": "24.69",
                "kia": "1",
                "sia": "1",
                "kea": "1",
                "sea": "1",
            }))
        );

        let psn = "7009-25KH_VBG0B3H5-3-3-28-4-416.901111";
        let val = v.ck_qr(psn);

        assert_eq!(
            val,
            Some(json!({
                "tech": "7009-25KH",
                "sn": "VBG0B3H5",
                "outdia": "-3",
                "india": "-3",
                "width": "-28",
                "standout": "-4",
                "dent": "-4",
                "angle": "16.90",
                "kia": "1",
                "sia": "1",
                "kea": "1",
                "sea": "1",
            }))
        );

        let psn = "7010-14K_ DT2D1072DAY2C3H7-4-3-27260.01111DA32D5H2-4-3-27259.01111-04-04-04";

        let val = v.ck_qr(psn);

        assert_eq!(
            val,
            Some(json!({
                "dt": true,
                "a": {
                    "sn": "DAY2C3H7",
                    "tech": "7010-14K",
                    "sea": "1",
                    "kea": "1",
                    "sia": "1",
                    "kia": "1",
                    "angle": "260.0",
                    "standout": "-04",
                    "outdia": "-4",
                    "india": "-3",
                    "width": "-27",
                    "groupsn": "2D1072",
                    "gtp": "A",
                    "group": "pair",
                    "other": "DA32D5H2",
                },
                "b": {
                    "sn": "DA32D5H2",
                    "tech": "7010-14K",
                    "sea": "1",
                    "kea": "1",
                    "sia": "1",
                    "kia": "1",
                    "angle": "259.0",
                    "standout": "-04",
                    "dent": "-04",
                    "outdia": "-4",
                    "india": "-3",
                    "width": "-27",
                    "groupsn": "2D1072",
                    "gtp": "B",
                    "group": "pair",
                    "other": "DAY2C3H7",
                },
            }))
        );

        let psn = "7010-18KHDD22H2L9-4-4-28+0-125.641111";

        let val = v.ck_qr(psn);

        assert_eq!(
            val,
            Some(json!({
                "tech": "7010-18KH",
                "sn": "DD22H2L9",
                "outdia": "-4",
                "india": "-4",
                "width": "-28",
                "standout": "+0",
                "dent": "-1",
                "angle": "25.64",
                "kia": "1",
                "sia": "1",
                "kea": "1",
                "sea": "1",
            }))
        );

        let psn = "#7008-27KH##BCQ2C7J8#-5*-4*-27*-2*-1#";

        let val = v.ck_qr(psn);

        assert_eq!(
            val,
            Some(json!({
                "tech": "7008-27KH",
                "sn": "BCQ2C7J8",
                "outdia": "-5",
                "india": "-4",
                "width": "-27",
                "standout": "-2",
                "dent": "-1",
            }))
        );

        let psn = "#7013-14KH#DT#4K1284#HCH2K6K3#-4*-3*-27#HCK0K0K8#-3*-2*-27*1*2*3*4#";

        let val = v.ck_qr(psn);

        assert_eq!(
            val,
            Some(json!({
                "dt": true,
                "a": {
                    "sn": "HCH2K6K3",
                    "tech": "7013-14KH",
                    "dent": "2",
                    "standout": "1",
                    "outdia": "-4",
                    "india": "-3",
                    "width": "-27",
                    "groupsn": "4K1284",
                    "gtp": "A",
                    "group": "pair",
                    "other": "HCK0K0K8",
                },
                "b": {
                    "sn": "HCK0K0K8",
                    "tech": "7013-14KH",
                    "dent": "4",
                    "standout": "3",
                    "outdia": "-3",
                    "india": "-2",
                    "width": "-27",
                    "groupsn": "4K1284",
                    "gtp": "B",
                    "group": "pair",
                    "other": "HCH2K6K3",
                },
            }))
        );

        let psn = "#3020#NN#5A0001#VAQ2C7J8#-5#VAR0C0L1#15#30#4.46.25#";

        let val = v.ck_qr(psn);

        assert_eq!(
            val,
            Some(json!({
                "sn": "VAQ2C7J8",
                "insn": "VAR0C0L1",
                "tech": "3020",
                "outdia": "-5",
                "india": "15",
                "play": "30",
                "angle": "4.46.25",
                "groupsn": "5A0001",
            }))
        );
    }
}
